﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using LFSRecord2.Events;
using LFSRecord2.Misc;
using LFSRecord2.Model;
using LFSRecord2.Model.UserActions;
using LFSRecord2.Structures;
using LFSRecord2.View.TimelineStructures;

namespace LFSRecord2.View.ViewportStructures
{
    /// <summary>
    /// Interaction logic for CompLayerCameraViewObject.xaml
    /// </summary>
    public partial class CompLayerLookCameraViewObject : Canvas
    {
        public static readonly DependencyProperty DoneUpdatingCurrentValuesProperty = DependencyProperty.Register(
            "DoneUpdatingCurrentValues",
            typeof(bool),
            typeof(CompLayerLookCameraViewObject),
            new FrameworkPropertyMetadata(new PropertyChangedCallback(OnDoneUpdatingCurrentValuesChanged))
        );
        public bool DoneUpdatingCurrentValues
        {
            get { return (bool)GetValue(DoneUpdatingCurrentValuesProperty); }
            set { SetValue(DoneUpdatingCurrentValuesProperty, value); }
        }
        private static void OnDoneUpdatingCurrentValuesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            CompLayerLookCameraViewObject compLayer = (CompLayerLookCameraViewObject)d;
            CompLayerLookCamera camLayer = (CompLayerLookCamera)compLayer.DataContext;

            if (camLayer.UpdatedCurrentValues == 0 || compLayer.CurrentView == ViewportViews.ThreeD)
                return;

            if (camLayer.HasParentLayer)
            {
                if (camLayer.ParentLayer.UpdatedCurrentValues > 0)
                    compLayer.redrawKeyframes();
            }
            else if (camLayer.UpdatedCurrentValues == CompLayerPropertyUpdateTypes.All)
            {
                compLayer.redrawKeyframes();
                compLayer.DrawSubRectangles(compLayer.PositionSubFrameRectangles, compLayer.PositionPointCache);
                compLayer.DrawSubRectangles(compLayer.POISubFrameRectangles, compLayer.POIPointCache);
            }
            //Debug.WriteLine("Updated properties (" + camLayer.LayerName + ") : " + camLayer.UpdatedCurrentValues);

            compLayer.redraw();
        }

        public static readonly DependencyProperty PositionPointCacheProperty = DependencyProperty.Register(
            "PositionPointCache",
            typeof(Collection<Point3DFrameCacheObject>),
            typeof(CompLayerLookCameraViewObject),
            new FrameworkPropertyMetadata(new PropertyChangedCallback(OnPositionPointCacheChanged))
        );
        public Collection<Point3DFrameCacheObject> PositionPointCache
        {
            get { return (Collection<Point3DFrameCacheObject>)GetValue(PositionPointCacheProperty); }
            set { SetValue(PositionPointCacheProperty, value); }
        }
        private static void OnPositionPointCacheChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            CompLayerLookCameraViewObject compLayer = (CompLayerLookCameraViewObject)d;
            compLayer.DrawSubRectangles(compLayer.PositionSubFrameRectangles, compLayer.PositionPointCache);
        }

        public static readonly DependencyProperty POIPointCacheProperty = DependencyProperty.Register(
            "POIPointCache",
            typeof(Collection<Point3DFrameCacheObject>),
            typeof(CompLayerLookCameraViewObject),
            new FrameworkPropertyMetadata(new PropertyChangedCallback(OnPOIPointCacheChanged))
        );
        public Collection<Point3DFrameCacheObject> POIPointCache
        {
            get { return (Collection<Point3DFrameCacheObject>)GetValue(POIPointCacheProperty); }
            set { SetValue(POIPointCacheProperty, value); }
        }
        private static void OnPOIPointCacheChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            CompLayerLookCameraViewObject compLayer = (CompLayerLookCameraViewObject)d;
            compLayer.DrawSubRectangles(compLayer.POISubFrameRectangles, compLayer.POIPointCache);
        }

        public static readonly DependencyProperty IsLayerSelectedProperty = DependencyProperty.Register(
            "IsLayerSelected",
            typeof(bool),
            typeof(CompLayerLookCameraViewObject),
            new FrameworkPropertyMetadata(new PropertyChangedCallback(OnIsLayerSelectedChanged))
        );
        public bool IsLayerSelected
        {
            get { return (bool)GetValue(IsLayerSelectedProperty); }
            set { SetValue(IsLayerSelectedProperty, value); }
        }
        private static void OnIsLayerSelectedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            CompLayerLookCameraViewObject compLayer = (CompLayerLookCameraViewObject)d;
            compLayer.redrawKeyframes();
            compLayer.redraw();
        }

        public double Scale = 1;
        public ViewportViews CurrentView = ViewportViews.Top;

        public CompLayerLookCameraViewObject()
        {
            InitializeComponent();

            Scale = LfsRecordController.ViewportScale;
            CurrentView = LfsRecordController.ViewportView;

            ServicesFactory.EventService.GetEvent<ViewportScaleChangedEvent>().Subscribe(ViewportScaleChangedHandler);
            ServicesFactory.EventService.GetEvent<ViewportViewChangedEvent>().Subscribe(ViewportViewChangedHandler);
        }

        private void CompLayerCameraViewObject_Loaded(object sender, RoutedEventArgs e)
        {
            CompLayerLookCamera camLayer = (CompLayerLookCamera)DataContext;
            camLayer.Disposed += OnDataContextDispose;

            // Setup bindings
            Binding myBinding = new Binding("DoneUpdatingCurrentValues");
            myBinding.Source = camLayer;
            SetBinding(CompLayerLookCameraViewObject.DoneUpdatingCurrentValuesProperty, myBinding);

            myBinding = new Binding("IsSelected");
            myBinding.Source = camLayer;
            SetBinding(CompLayerLookCameraViewObject.IsLayerSelectedProperty, myBinding);

            // Position
            //myBinding = new Binding("AnimationPath");
            //myBinding.Source = (CompLayerPropertyPoint3D)camLayer.Properties[(int)CompLayerLookCamera.PropertyTypes.Position];
            //PositionPath.SetBinding(Path.DataProperty, myBinding);

            myBinding = new Binding("Point3DFrameCache");
            myBinding.Source = (CompLayerPropertyPoint3D)camLayer.Properties[(int)CompLayerLookCamera.PropertyTypes.Position];
            SetBinding(CompLayerLookCameraViewObject.PositionPointCacheProperty, myBinding);

            // POI
            //myBinding = new Binding("AnimationPath");
            //myBinding.Source = (CompLayerPropertyPoint3D)camLayer.Properties[(int)CompLayerLookCamera.PropertyTypes.LookAt];
            //POIPath.SetBinding(Path.DataProperty, myBinding);

            myBinding = new Binding("Point3DFrameCache");
            myBinding.Source = (CompLayerPropertyPoint3D)camLayer.Properties[(int)CompLayerLookCamera.PropertyTypes.LookAt];
            SetBinding(CompLayerLookCameraViewObject.POIPointCacheProperty, myBinding);

            //createProjectedMeshPoints();
            redraw();
        }

        private void OnDataContextDispose(object sender, EventArgs e)
        {
            ServicesFactory.EventService.GetEvent<ViewportScaleChangedEvent>().Unsubscribe(ViewportScaleChangedHandler);
            ServicesFactory.EventService.GetEvent<ViewportViewChangedEvent>().Unsubscribe(ViewportViewChangedHandler);
        }

        private void PositionKeyFramesControl_Loaded(object sender, RoutedEventArgs e)
        {
            CompLayerLookCamera nullLayer = (CompLayerLookCamera)DataContext;

            //PositionKeyFramesControl.ItemsSource = nullLayer.Properties[(int)CompLayerLookCamera.PropertyTypes.Position].KeyFrames;
            Binding myBinding = new Binding("KeyFrames");
            myBinding.Source = nullLayer.Properties[(int)CompLayerLookCamera.PropertyTypes.Position];
            PositionKeyFramesControl.SetBinding(ItemsControl.ItemsSourceProperty, myBinding);
            //POIKeyFramesControl.ItemsSource = nullLayer.Properties[(int)CompLayerLookCamera.PropertyTypes.LookAt].KeyFrames;
        }

        private void POIKeyFramesControl_Loaded(object sender, RoutedEventArgs e)
        {
            CompLayerLookCamera nullLayer = (CompLayerLookCamera)DataContext;

            //PositionKeyFramesControl.ItemsSource = nullLayer.Properties[(int)CompLayerLookCamera.PropertyTypes.Position].KeyFrames;
            POIKeyFramesControl.ItemsSource = nullLayer.Properties[(int)CompLayerLookCamera.PropertyTypes.LookAt].KeyFrames;
        }

        public void ViewportScaleChangedHandler(double scale)
        {
            //Debug.WriteLine("CameraViewObject Scale changed : " + scale);
            Scale = scale;

            CompLayerLookCamera camLayer = (CompLayerLookCamera)DataContext;
            if (camLayer.Properties.Count == 0)
            {
                Debug.WriteLine("Euhm, wanted to draw a camera layer with no properties? (" + LfsRecordController.project.Layers.Count + ")");
                return;
            }

            //createProjectedMeshPoints();
            redraw();

            updateKeyFrameDrawingScales();
            DrawSubRectangles(PositionSubFrameRectangles, PositionPointCache);
            DrawSubRectangles(POISubFrameRectangles, POIPointCache);
        }

        public void ViewportViewChangedHandler(ViewportViewChangedEventArgs e)
        {
            CurrentView = e.View;

            if (CurrentView != ViewportViews.ThreeD)
            {
                //createProjectedMeshPoints();
                redraw();
                DrawSubRectangles(PositionSubFrameRectangles, PositionPointCache);
                DrawSubRectangles(POISubFrameRectangles, POIPointCache);
            }

            Collection<CompLayerKeyFrameDrawing> list;
            CompLayerLookCamera camLayer = (CompLayerLookCamera)DataContext;
            for (int a = 0; a < camLayer.Properties[(int)CompLayerLookCamera.PropertyTypes.Position].KeyFrames.Count; a++)
            {
                list = VisualHelper.FindVisualChildren<CompLayerKeyFrameDrawing>(PositionKeyFramesControl.ItemContainerGenerator.ContainerFromIndex(a));

                foreach (CompLayerKeyFrameDrawing kfd in list)
                {
                    kfd.ViewportViewChangedHandler(CurrentView);
                }
            }

            for (int a = 0; a < camLayer.Properties[(int)CompLayerLookCamera.PropertyTypes.LookAt].KeyFrames.Count; a++)
            {
                list = VisualHelper.FindVisualChildren<CompLayerKeyFrameDrawing>(POIKeyFramesControl.ItemContainerGenerator.ContainerFromIndex(a));

                foreach (CompLayerKeyFrameDrawing kfd in list)
                {
                    kfd.ViewportViewChangedHandler(CurrentView);
                }
            }
        }

        private void updateKeyFrameDrawingScales()
        {
            Collection<CompLayerKeyFrameDrawing> list;
            CompLayerLookCamera camLayer = (CompLayerLookCamera)DataContext;
            for (int a = 0; a < camLayer.Properties[(int)CompLayerLookCamera.PropertyTypes.Position].KeyFrames.Count; a++)
            {
                list = VisualHelper.FindVisualChildren<CompLayerKeyFrameDrawing>(PositionKeyFramesControl.ItemContainerGenerator.ContainerFromIndex(a));

                foreach (CompLayerKeyFrameDrawing kfd in list)
                {
                    kfd.ViewportScaleChangedHandler(Scale);
                }
            }

            for (int a = 0; a < camLayer.Properties[(int)CompLayerLookCamera.PropertyTypes.LookAt].KeyFrames.Count; a++)
            {
                list = VisualHelper.FindVisualChildren<CompLayerKeyFrameDrawing>(POIKeyFramesControl.ItemContainerGenerator.ContainerFromIndex(a));

                foreach (CompLayerKeyFrameDrawing kfd in list)
                {
                    kfd.ViewportScaleChangedHandler(Scale);
                }
            }

            PositionPath.StrokeThickness = 0.5 / Scale;
            POIPath.StrokeThickness = 0.5 / Scale;
        }

        private void redrawKeyframes()
        {
            Collection<CompLayerKeyFrameDrawing> list;
            CompLayerLookCamera camLayer = (CompLayerLookCamera)DataContext;
            if (!camLayer.IsSelected)
                return;

            for (int a = 0; a < camLayer.Properties[(int)CompLayerLookCamera.PropertyTypes.Position].KeyFrames.Count; a++)
            {
                list = VisualHelper.FindVisualChildren<CompLayerKeyFrameDrawing>(PositionKeyFramesControl.ItemContainerGenerator.ContainerFromIndex(a));

                foreach (CompLayerKeyFrameDrawing kfd in list)
                {
                    kfd.redraw();
                }
            }

            for (int a = 0; a < camLayer.Properties[(int)CompLayerLookCamera.PropertyTypes.LookAt].KeyFrames.Count; a++)
            {
                list = VisualHelper.FindVisualChildren<CompLayerKeyFrameDrawing>(POIKeyFramesControl.ItemContainerGenerator.ContainerFromIndex(a));

                foreach (CompLayerKeyFrameDrawing kfd in list)
                {
                    kfd.redraw();
                }
            }

            PositionPath.StrokeThickness = 0.5 / Scale;
            POIPath.StrokeThickness = 0.5 / Scale;
        }

        private void redraw()
        {
            CompLayerLookCamera camLayer = (CompLayerLookCamera)DataContext;
            //Debug.WriteLine("Getting mesh");

            // Retrieve a transformed layer object (mesh)
            CompLayerMeshBase layerObjectMesh = camLayer.CreateTransformedMesh(LfsRecordController.FrameIndex, new Vector3D(1 / Scale, 1 / Scale, 1 / Scale));

            // Draw the new MainObject geometry
            GeometryGroup tlrGroup = new GeometryGroup();
            LineGeometry lineGeom;
            for (int a = 0; a < layerObjectMesh.NumLineIndexes; a++)
            {
                lineGeom = new LineGeometry(
                       new Point(layerObjectMesh.Points[layerObjectMesh.LineIndexes[a]].X, layerObjectMesh.Points[layerObjectMesh.LineIndexes[a]].Y),
                       new Point(layerObjectMesh.Points[layerObjectMesh.LineIndexes[a + 1]].X, layerObjectMesh.Points[layerObjectMesh.LineIndexes[a + 1]].Y));
                lineGeom.Freeze();
                tlrGroup.Children.Add(lineGeom);

                a++;
            }

            // Cam to POI line
            lineGeom = new LineGeometry(new Point(layerObjectMesh.CenterPoint.X, layerObjectMesh.CenterPoint.Y), new Point(layerObjectMesh.LookAtPoint.X, layerObjectMesh.LookAtPoint.Y));
            lineGeom.Freeze();
            tlrGroup.Children.Add(lineGeom);

            EllipseGeometry ellipseGeom;
            ellipseGeom = new EllipseGeometry(new Point(layerObjectMesh.LookAtPoint.X, layerObjectMesh.LookAtPoint.Y), 1 / Scale, 1 / Scale);
            ellipseGeom.Freeze();
            tlrGroup.Children.Add(ellipseGeom);

            // Add it to the drawer object
            GeometryDrawing gd = new GeometryDrawing();
            gd.Geometry = tlrGroup;
            gd.Pen = new Pen(((IsLayerSelected) ? Brushes.White : new SolidColorBrush(Color.FromArgb(255, 180, 180, 180))), 1 / Scale);
            gd.Freeze();

            // Apply it to the MainObject Image
            MainObject.Source = new DrawingImage(gd);

            MainObject.RenderTransform = new TranslateTransform(
                gd.Bounds.Left,
                gd.Bounds.Top);

            // Position crosshair
            TransformGroup trGroup = new TransformGroup();
            trGroup.Children.Add(new ScaleTransform(1 / Scale, 1 / Scale));
            trGroup.Children.Add(new TranslateTransform(layerObjectMesh.CenterPoint.X, layerObjectMesh.CenterPoint.Y));
            PositionCrossHair.RenderTransform = trGroup;

            // Funky look-at crosshair parent ignore exception
            Matrix3D mTr;
            if (camLayer.HasParentLayer &&
                camLayer.Properties[(int)CompLayerLookCamera.PropertyTypes.LookAt].HasBoundProperty &&
                camLayer.Properties[(int)CompLayerLookCamera.PropertyTypes.LookAt].BoundPropertyIgnoreParent)
                mTr = new Matrix3D();
            else
                mTr = camLayer.GetParentedTransformMatrixAtFrame(LfsRecordController.FrameIndex, false);
            mTr = Matrix3D.Multiply(mTr, LfsRecordController.ViewTransformMatrix);

            Point3D lookAt = mTr.Transform((Point3D)((CompLayerPropertyPoint3D)camLayer.Properties[(int)CompLayerLookCamera.PropertyTypes.LookAt]).GetValueAtFrame(LfsRecordController.FrameIndex));

            TransformGroup trGroup2 = new TransformGroup();
            trGroup2.Children.Add(new ScaleTransform(1 / Scale, 1 / Scale));
            trGroup2.Children.Add(new TranslateTransform(lookAt.X, lookAt.Y));
            POICrossHair.RenderTransform = trGroup2;

            if (camLayer.HasParentLayer)
            {
                DrawSubRectangles(PositionSubFrameRectangles, PositionPointCache);
                DrawSubRectangles(POISubFrameRectangles, POIPointCache);
            }
        }

        private void DrawSubRectangles(Image targetImage, Collection<Point3DFrameCacheObject> pointCache)
        {
            //Debug.WriteLine("redrawing sub frame rectangles - " + targetImage.Name);
            CompLayerLookCamera camLayer = (CompLayerLookCamera)DataContext;

            if (pointCache == null || pointCache.Count == 0)
            {
                targetImage.Source = null;
                if (targetImage.Name == "PositionSubFrameRectangles")
                    PositionPath.Data = null;
                else
                    POIPath.Data = null;
                return;
            }

            Matrix3D m = Matrix3D.Multiply(camLayer.GetParentedTransformMatrixAtFrame(LfsRecordController.FrameIndex, false), LfsRecordController.ViewTransformMatrix);
            Point3D p;
            
            GeometryGroup lineGeomGroup = new GeometryGroup();
            StreamGeometry lineGeometry = new StreamGeometry();
            StreamGeometryContext ctxLine = lineGeometry.Open();

            GeometryGroup rectGeomGroup = new GeometryGroup();
            StreamGeometry rectGeometry;
            rectGeomGroup.FillRule = FillRule.Nonzero;

            //int currentFrame = (prop.KeyFrames.Count > 0) ? prop.KeyFrames[0].FrameNum : 0;
            double rectRadius = 0.75 * 1 / Scale;
            for (int a = 0; a < pointCache.Count; a++)
            {
                p = m.Transform(pointCache[a].Value);

                if (camLayer.IsSelected)
                {
                    rectGeometry = new StreamGeometry();
                    using (StreamGeometryContext ctxRect = rectGeometry.Open())
                    {
                        ctxRect.BeginFigure(new Point(p.X - rectRadius, p.Y), false, false);
                        ctxRect.LineTo(new Point(p.X + rectRadius, p.Y), true, false);
                    }
                    rectGeometry.Freeze();
                    rectGeomGroup.Children.Add(rectGeometry);
                }

                // line
                if (a == 0)
                {
                    ctxLine.BeginFigure(new Point(p.X, p.Y), false, false);
                }
                else
                {
                    ctxLine.LineTo(new Point(p.X, p.Y), true, false);
                }
            }
            ctxLine.Close();

            if (camLayer.IsSelected)
            {
                GeometryDrawing gd = new GeometryDrawing();
                gd.Geometry = rectGeomGroup;
                gd.Brush = Brushes.Beige;
                gd.Pen = new Pen(Brushes.White, rectRadius * 2);
                gd.Freeze();

                targetImage.Source = new DrawingImage(gd);
                targetImage.RenderTransform = new TranslateTransform(gd.Bounds.Left, gd.Bounds.Top);
            }

            // Now populate the path
            lineGeometry.Freeze();
            lineGeomGroup.Children.Add(lineGeometry);

            if (targetImage.Name == "PositionSubFrameRectangles")
                PositionPath.Data = lineGeometry;
            else
                POIPath.Data = lineGeometry;
        }

        CompLayerPropertyPoint3D _propDrag;
        Point _prevPoint;
        bool _objectHasMoved;
        bool _poiSecondaryMoved;
        Vector3D _objectMoveVec;
        Vector3D _poiSecondaryMoveVec;
        bool _hasUnderlyingKeyFrame = false;
        private void Crosshair_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            e.Handled = true;
            CompLayerLookCamera camLayer = (CompLayerLookCamera)DataContext;
            if (((ViewportObjectCrosshair)sender).ID == "POI")
            {
                //Debug.WriteLine("CrossHair POI LEFT clicked");
                _propDrag = (CompLayerPropertyPoint3D)camLayer.Properties[(int)CompLayerLookCamera.PropertyTypes.LookAt];
            }
            else
            {
                //Debug.WriteLine("CrossHair Position LEFT clicked");
                _propDrag = (CompLayerPropertyPoint3D)camLayer.Properties[(int)CompLayerLookCamera.PropertyTypes.Position];
            }

            if (_propDrag.HasBoundProperty)
                return;

            if (_propDrag.IsAnimated)
            {
                // Use underlying keyframe?
                _hasUnderlyingKeyFrame = true;
                CustomPoint3DKeyFrame kfDrag = (CustomPoint3DKeyFrame)_propDrag.GetKeyAtTime(LfsRecordController.FrameIndex);
                if (kfDrag == null)
                {
                    //Debug.WriteLine("prop has NO keyframe");
                    _hasUnderlyingKeyFrame = false;

                    if (!camLayer.IsSelected)
                    {
                        LfsRecordController.project.DeselectAll();
                        camLayer.IsSelected = true;
                        _propDrag.IsSelected = true;
                    }
                }
                else
                {
                    // Deselect all if we're not selected
                    if (!kfDrag.IsSelected && !Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift))
                        LfsRecordController.project.DeselectAll();
                    kfDrag.IsSelected = true;
                }
            }
            else
            {
                _hasUnderlyingKeyFrame = false;
                LfsRecordController.project.DeselectAll();
                camLayer.IsSelected = true;
                _propDrag.IsSelected = true;
            }

            // Start mouse move capturing
            Application.Current.MainWindow.MouseMove += Crosshair_MouseMove;
            Application.Current.MainWindow.MouseUp += Crosshair_MouseUp;
            _prevPoint = e.GetPosition(null);

            this.IsHitTestVisible = false;
            _objectHasMoved = false;
            _poiSecondaryMoved = false;
            _objectMoveVec = new Vector3D();
            _poiSecondaryMoveVec = new Vector3D();
        }

        private void Crosshair_MouseUp(object sender, MouseButtonEventArgs e)
        {
            e.Handled = true;
            this.IsHitTestVisible = true;

            Application.Current.MainWindow.MouseMove -= Crosshair_MouseMove;
            Application.Current.MainWindow.MouseUp -= Crosshair_MouseUp;

            if (_objectHasMoved)
            {
                // Record user action
                //Debug.WriteLine("Recording p3 vector : {0}, {1}, {2}", _objectMoveVec.X, _objectMoveVec.Y, _objectMoveVec.Z);
                if (_hasUnderlyingKeyFrame)
                    LfsRecordController.project.SubmitUserActionSelectedKeyFramePoint3DValueChange(new Vector3D(-_objectMoveVec.X, -_objectMoveVec.Y, -_objectMoveVec.Z));
                else
                    LfsRecordController.RecordUserAction(new UserActionKeyFramePoint3DValueChanged()
                        {
                            LayerIndex = _propDrag.ParentLayer.LayerIndex,
                            PropertyIndex = _propDrag.PropertyIndex,
                            KeyFrameIndex = -1,
                            ValueOffset = new Vector3D(-_objectMoveVec.X, -_objectMoveVec.Y, -_objectMoveVec.Z)
                        });

                // did we drag position crosshair, and did we also move lookat crosshair?
                if (_propDrag.PropertyName == "Position" && _poiSecondaryMoved)
                {
                    CompLayerLookCamera camLayer = (CompLayerLookCamera)DataContext;
                    CompLayerPropertyPoint3D poiProp = (CompLayerPropertyPoint3D)camLayer.Properties[(int)CompLayerLookCamera.PropertyTypes.LookAt];
                    CustomPoint3DKeyFrame kf = (CustomPoint3DKeyFrame)poiProp.GetKeyAtTime(LfsRecordController.FrameIndex);
                    if (kf == null)
                    {
                        LfsRecordController.RecordUserAction(new UserActionKeyFramePoint3DValueChanged()
                        {
                            LayerIndex = poiProp.ParentLayer.LayerIndex,
                            PropertyIndex = poiProp.PropertyIndex,
                            KeyFrameIndex = -1,
                            ValueOffset = new Vector3D(-_poiSecondaryMoveVec.X, -_poiSecondaryMoveVec.Y, -_poiSecondaryMoveVec.Z)
                        });
                    }
                    else 
                    {
                        if (!kf.IsSelected)
                        {
                            LfsRecordController.RecordUserAction(new UserActionKeyFramePoint3DValueChanged()
                            {
                                LayerIndex = poiProp.ParentLayer.LayerIndex,
                                PropertyIndex = poiProp.PropertyIndex,
                                KeyFrameIndex = kf.KeyFrameIndex,
                                ValueOffset = new Vector3D(-_poiSecondaryMoveVec.X, -_poiSecondaryMoveVec.Y, -_poiSecondaryMoveVec.Z)
                            });
                        }
                    }
                }

                LfsRecordController.CreateUndoLevel();
            }
            _objectHasMoved = false;
            _propDrag = null;
        }

        private void Crosshair_MouseMove(object sender, MouseEventArgs e)
        {
            Point mPos = e.GetPosition(null);
            Vector3D diff3D = new Vector3D(_prevPoint.X - mPos.X, _prevPoint.Y - mPos.Y, 0);
            _prevPoint = mPos;

            if (diff3D.X == 0 && diff3D.Y == 0)
                return;
            _objectHasMoved = true;

            CompLayerLookCamera camLayer = (CompLayerLookCamera)DataContext;

            Matrix3D invMat = Matrix3D.Multiply(camLayer.GetParentedTransformMatrixAtFrame(LfsRecordController.FrameIndex, false), LfsRecordController.ViewTransformMatrix);
            // If we're moving LookAt, also add the camera's orientation

            invMat.OffsetX = 0;
            invMat.OffsetY = 0;
            invMat.OffsetZ = 0;
            invMat.Invert();
            diff3D = invMat.Transform(diff3D);

            _objectMoveVec += diff3D;

            if (!_hasUnderlyingKeyFrame && _propDrag.IsAnimated)
            {
                //Debug.WriteLine("PropDrag ID : " + _propDrag.PropertyName);
                // Set new keyframe
                CustomPoint3DKeyFrame kfDrag = (CustomPoint3DKeyFrame)_propDrag.SetKeyAtCurrentTime(LfsRecordController.FrameIndex);
                if (kfDrag == null)
                    return;

                LfsRecordController.project.DeselectAll();
                kfDrag.IsSelected = true;
                _hasUnderlyingKeyFrame = true;

                // Record user action of added keyframe (Prepend this one, because several bezier controls will probably have changed as well)
                LfsRecordController.RecordUserAction(new UserActionAddKeyframe()
                    {
                        LayerIndex = kfDrag.ParentLayer.LayerIndex,
                        PropertyIndex = kfDrag.ParentProperty.PropertyIndex,
                        KeyFrame = kfDrag.DataStructure()
                    },
                    true);
            }

            //Debug.WriteLine("Moving by : {0}, {1}", diff.X, diff.Y);
            if (_propDrag.IsAnimated)
            {
                LfsRecordController.project.TranslateSelectedKeyFrames(diff3D.X / Scale, diff3D.Y / Scale, diff3D.Z / Scale);

                // If we're moving Position, must we move POI?
                if (_propDrag.PropertyName == "Position" && (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)))
                {
                    CompLayerPropertyPoint3D poiProp = (CompLayerPropertyPoint3D)camLayer.Properties[(int)CompLayerLookCamera.PropertyTypes.LookAt];
                    CustomPoint3DKeyFrame kf = (CustomPoint3DKeyFrame)poiProp.GetKeyAtTime(LfsRecordController.FrameIndex);
                    if (kf == null && poiProp.IsAnimated)
                    {
                        // We first have to create a new keyframe
                        CustomPoint3DKeyFrame kfPoi = (CustomPoint3DKeyFrame)poiProp.SetKeyAtCurrentTime(LfsRecordController.FrameIndex);
                        if (kfPoi != null)
                        {
                            LfsRecordController.RecordUserAction(new UserActionAddKeyframe()
                            {
                                LayerIndex = kfPoi.ParentLayer.LayerIndex,
                                PropertyIndex = kfPoi.ParentProperty.PropertyIndex,
                                KeyFrame = kfPoi.DataStructure()
                            },
                            true);

                            kfPoi.Value = new Point3D(
                                kfPoi.Value.X - diff3D.X / Scale,
                                kfPoi.Value.Y - diff3D.Y / Scale,
                                kfPoi.Value.Z - diff3D.Z / Scale
                                );

                            _poiSecondaryMoveVec += diff3D;
                            _poiSecondaryMoved = true;
                        }
                    }
                    else if (kf != null && !kf.IsSelected)
                    {
                        poiProp.CurrentXValue -= diff3D.X / Scale;
                        poiProp.CurrentYValue -= diff3D.Y / Scale;
                        poiProp.CurrentZValue -= diff3D.Z / Scale;
                        poiProp.OnCurrentValueChange();

                        _poiSecondaryMoveVec += diff3D;
                        _poiSecondaryMoved = true;
                    }
                }
            }
            else
            {
                _propDrag.CurrentXValue -= diff3D.X / Scale;
                _propDrag.CurrentYValue -= diff3D.Y / Scale;
                _propDrag.CurrentZValue -= diff3D.Z / Scale;
                _propDrag.OnCurrentValueChange();

                // If we're moving Position, must we move POI?
                if (_propDrag.PropertyName == "Position" && (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)))
                {
                    CompLayerPropertyPoint3D poiProp = (CompLayerPropertyPoint3D)camLayer.Properties[(int)CompLayerLookCamera.PropertyTypes.LookAt];
                    poiProp.CurrentXValue -= diff3D.X / Scale;
                    poiProp.CurrentYValue -= diff3D.Y / Scale;
                    poiProp.CurrentZValue -= diff3D.Z / Scale;
                    poiProp.OnCurrentValueChange();
                }
            }

            //createProjectedMeshPoints();
            if (!_hasUnderlyingKeyFrame)
            {
                camLayer.SetCurrentTransformMatrix();
                redraw();
            }

            if (((CompLayerLookCamera)DataContext).IsActiveCamera)
                ServicesFactory.EventService.GetEvent<CompLayerPropertyChangedEvent>().Publish(new CompLayerPropertyChangedEventArgs() { Sender = this, PropertyType = "Value", UpdateCurrentValues = false });
        }

        private void Crosshair_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            CompLayerLookCamera camLayer = (CompLayerLookCamera)DataContext;
            if (((ViewportObjectCrosshair)sender).ID == "POI")
                _propDrag = (CompLayerPropertyPoint3D)camLayer.Properties[(int)CompLayerLookCamera.PropertyTypes.LookAt];
            else
                _propDrag = (CompLayerPropertyPoint3D)camLayer.Properties[(int)CompLayerLookCamera.PropertyTypes.Position];
            if (_propDrag.HasBoundProperty)
                return;

            // Use underlying keyframe
            CustomPoint3DKeyFrame kf = (CustomPoint3DKeyFrame)_propDrag.GetKeyAtTime(LfsRecordController.FrameIndex);
            if (kf != null)
            {
                e.Handled = true;
                KeyImageControl ki = new KeyImageControl();
                ki.DataContext = kf;
                ki.SetupContextMenu();
                ki.ContextMenu.IsOpen = true;
            }
            _propDrag = null;
        }
    }
}
